package ru.stork.vasilisa.lang

import ru.stork.vasilisa.exceptions.ValislisaParsingException
import util.parsing.combinator.syntactical.StdTokenParsers
import util.parsing.combinator.lexical.StdLexical
import util.parsing.input.Reader
import java.io.File

object VasilisaLanguageParser {

  @throws(classOf[ValislisaParsingException])
  def apply(input: Reader[Char]): Node = apply(input, None)

  @throws(classOf[ValislisaParsingException])
  def apply(input: Reader[Char], source:Option[File]): Node = {
    new VasilisaLanguageParser(source).getNode(input)
  }

}

/**
 * @example root: Node = VasilisaLanguageParser(streamReader)
 */
class VasilisaLanguageParser(val source:Option[File]) extends StdTokenParsers {

  val comma = ","
  val dot = "."

  implicit val _source = source

  /**
   *  Thanks to http://code.google.com/p/scalapatterns
   */
  class LexicalWithCommnets extends StdLexical() {

    import scala.util.parsing.input.CharArrayReader.EofCh

    override def whitespace: Parser[Any] = rep(
        whitespaceChar
        | '/' ~ '*' ~ comment
        | '/' ~ '/' ~ rep( chrExcept(EofCh, '\n') )
        | '/' ~ '*' ~ failure("unclosed comment")
    )
  }

  type Tokens = StdLexical
  val lexical = new LexicalWithCommnets();

  lexical.delimiters ++= List(
    "<", ">",
    "{", "}",
    comma, dot
  )

  lexical.reserved ++= List(
    "trait", "model", "struct",
    "extends", "include", "version",
    "variant", "package", "of"
  )

  private def pkg: Parser[Node] = {
    def impBody: Parser[Import] = repsep(ident, dot) ~ dot ~ ident ^^ { case pkg~_~n => Import(pkg, n) }
    val imp = "import" ~> impBody
    ("package" ~> repsep(ident, dot) ~ rep(imp) ~ vtype) ^^ { case n~i~t => Package(n, i, t) }
  }

  // TODO This is pretty but we need a normal tests.
  private def vtype: Parser[TypeNode] = {

    // Parser for Vasilisp
    /*val _convertor = "convertor" ~> _fun
    val _args = "(" ~> repsep(_prop, comma) <~ ")"
    val _fundef = "fun" ~> ident ~ _args ~ _fun

    def _fun = {
      def _expr = _fun | _var | _lit
      def _var = "#" ~> repsep(ident, ".") ^^ { case chain => Var(chain) }
      def _lit = ident ^^ { case x => Lit(x) }

      "(" ~> ident ~ rep(_expr) <~ ")" ^^ { case name ~ exprs => Fun(name, exprs) }
    }*/

    val _params = opt("<" ~> repsep(ident, comma) <~ ">")
    def _unit = "{" ~> rep( _prop | _version | vtype) <~ "}"
    def _version: Parser[Version] = ("version" ~> numericLit ~ rep(_prop)) ^^ { case n ~ u => Version(n toInt, u) }
    def _prop = ident ~ "of" ~ _td ^^ { case n ~ _ ~ t => Property(n, t) }
    def _td:Parser[TypeDeclaration] = ident ~ opt("<" ~> repsep(_td, comma) <~ ">") ^^ { case name ~ params => TypeDeclaration(name, params) }

    def _variant = ( "variant" ~> ident ~ _params ~ _unit ) ^^ { case n ~ p ~ u => Variant(n, p, u) }
    def _model: Parser[Model] = {
      ( "model" ~> ident ~ _params ~ opt("extends" ~> _td) ~ _unit ) ^^ { case n ~ p ~ s ~ u => Model(n, p, u, s) }
    }
    _model | _variant
  }

  @throws(classOf[ValislisaParsingException])
  def getNode(input: Reader[Char]): Node = {
    phrase(pkg)(new lexical.Scanner(input)) match {
      case Success(node, in) => node
      case Error(mess, in) => throw new ValislisaParsingException(mess, in.pos)
      case Failure(mess, in) => throw new ValislisaParsingException(mess, in.pos)
    }
  }

}